package com.study;

import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;

/**
 * @Author: leo
 * @date: 2022/4/19
 */
public class WebProxy {

    public static void main(String[] args) throws IOException {
        int port = 8087;
        //监听端口
        ServerSocket serverSocket = new ServerSocket(port);
        for (; ; ) {
            new SocketHandle(serverSocket.accept()).start();
        }
    }

    static class SocketHandle extends Thread {

        private Socket socket;

        public SocketHandle(Socket socket) {
            this.socket = socket;
        }

        /**
         * HTTP工作流：
         * <p>
         * 1、客户端浏览器将请求直接发送到代理服务器
         * <p>
         * 2、代理服务器从Http Header获取目标主机地址，向目标主机发送请求
         * <p>
         * 3、目标主机将响应返回到代理服务器
         * <p>
         * 4、代理服务器向客户端浏览器返回响应。
         * <p>
         * HTTPS是在HTTP上建立SSL加密层，对传输数据进行加密的，是HTTP协议的安全版。
         * <p>
         * 一、主要作用是：
         * <p>
         * 1、建立信息安全通道，对数据进行加密，确保传输中的数据安全；
         * <p>
         * 2、对网站的服务器进行真正的身份认证。
         * <p>
         * 二、主要流程如下
         * <p>
         * 1、浏览器首先向代理服务器发送HTTP Connect请求，发送目标主机信息。
         * <p>
         * 2、代理服务器建立与目标主机的tcp链接，向浏览器响应连接已建立的响应。
         * <p>
         * 3、浏览器向代理服务器发送请求，代理服务器向目标主机发送请求。
         * <p>
         * 4、目标主机响应代理服务器，代理服务器响应浏览器。
         */

        @Override
        public void run() {
            //浏览器输出流
            OutputStream browserOutput = null;
            //浏览器输入流
            InputStream browserInput = null;
            //代理socket客户端
            Socket proxySocketClient = null;
            //代理socket的输入流
            InputStream proxyInput = null;
            //代理socket的输出流
            OutputStream proxyOutput = null;
            BufferedReader bufr = null;
            try {
                browserInput = socket.getInputStream();
                browserOutput = socket.getOutputStream();
                String line;
                String host = "";
                InputStreamReader isr = new InputStreamReader(browserInput);
                bufr = new BufferedReader(isr);
                StringBuilder headStr = new StringBuilder();
                System.out.println("Request timestamp:" + System.currentTimeMillis());
                //读取HTTP请求头，并拿到HOST请求头和method
                while (null != (line = bufr.readLine())) {
                    System.out.println(line);
                    headStr.append(line + "\r\n");
                    if (line.length() == 0) {
                        break;
                    } else {
                        String[] temp = line.split(" ");
                        if (temp[0].contains("Host")) {
                            host = temp[1];
                        }
                    }
                }
                String type = headStr.substring(0, headStr.indexOf(" "));
                //根据host头解析出目标服务器的host和port
                String[] hostTemp = host.split(":");
                host = hostTemp[0];
                int port = 80;
                if (hostTemp.length > 1) {
                    port = Integer.valueOf(hostTemp[1]);
                }
                //链接到目标服务器
                proxySocketClient = new Socket(host, port);
                proxyInput = proxySocketClient.getInputStream();
                proxyOutput = proxySocketClient.getOutputStream();
                //根据HTTP method来判断是https还是http请求
                if ("CONNECT".equalsIgnoreCase(type)) {//https先创建隧道
                    browserOutput.write("HTTP/1.1 200 Connection Established\r\n\r\n".getBytes());
                    browserOutput.flush();
                } else {//http直接将请求头转发
                    proxyOutput.write(headStr.toString().getBytes());
                }
                //新开线程转发客户端请求至目标服务器
                new ProxyHandleThread(browserInput, proxyOutput).start();
                //转发目标服务器响应至客户端

                while (true) {
                    browserOutput.write(proxyInput.read());
                }
//
            } catch (IOException e) {
                //socket连接读取超时的时候会进入到这里
            } finally {
                if (proxyInput != null) {
                    try {
                        proxyOutput.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
                if (bufr != null) {
                    try {
                        bufr.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
                if (proxyOutput != null) {
                    try {
                        proxyOutput.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
                if (proxySocketClient != null) {
                    try {
                        proxySocketClient.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
                if (browserInput != null) {
                    try {
                        browserInput.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
                if (browserOutput != null) {
                    try {
                        browserOutput.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
                if (socket != null) {
                    try {
                        socket.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            }

        }
    }

    static class ProxyHandleThread extends Thread {

        private InputStream input;
        private OutputStream output;

        public ProxyHandleThread(InputStream input, OutputStream output) {
            this.input = input;
            this.output = output;
        }

        @Override
        public void run() {
            try {
                while (true) {
                    output.write(input.read());
                }
            } catch (IOException e) {
                //当socket关闭连接以后，重新读取，会抛出java.net.SocketException: Software caused connection abort: socket write error异常
            }
        }
    }
}
